/**
 * Debugmode Frameserver
 * Copyright (C) 2002-2009 Satish Kumar, All Rights Reserved
 * http://www.debugmode.com/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */

#include <windows.h>
#include <tchar.h>
#include <math.h>
#include <mmsystem.h>
#include <stdio.h>
#include <commctrl.h>
#include <winsock.h>
#include "PremiereFS.h"
#include "../dfsc/dfsc.h"
#include "resource.h"
#include "blankavi.h"
#include "utils.h"
#include "fscommon.h"

HINSTANCE ghInst;

#define APPNAME "DebugMode FrameServer"

bool prPro = false;

// Premiere Elements 1.0 has this bug in the API where they mistakenly added two fields at the end of
// compOutputRec7 which is contained in compDoCompileInfo7, so all fields of compDoCompileInfo7 after
// compOutputRec7 are offset by 8 bytes and the plugin does not know about it. So things dont work.
// Here is the data for working around this bug.
// BEGIN {

bool workaroundPremElementsBug = false;

#define prCompileVersion750         5           // 7.50
#define prCompileVersion760         6           // 7.60
#define COMPILEMOD_VERSION_5        prCompileVersion750
#define COMPILEMOD_VERSION_6        prCompileVersion760

typedef struct {
  long compilerID;
  void* compilerPrefs;                         // Private compiler data
  compOutputRec7 outputRec;
  unsigned char bad_addition_to_api[8];
  compPostProcessing7 postProcessing;
  compFileSpec outputFile;                     // During compDoCompile: <in> the filename to generate
                                               // During compDoCustomCompile: <in/out> the file that was
                                               // generated by the compiler.
  compFileRef outputFileRef;
  long startFrame;
  long endFrame;                             // This is exclusive (i.e. for (frame=startFrame; frame<endFrame; frame++) )
  long compileSeqID;                         // This must be passed to compGetFrame and GetAudio (audio callback).

  PrTimelineID timelineData;                 // Handle that can be used with the prSDKPlugTimeline.h callback suite
  long fileType;                             // 6.0  -- fileType we're compiling (in case compiler does several)
                                             // Initialized by host -- do not change!
  long isClipCompile;                        // 6.0: true if we are compiling from a clip (not the timeline)
} compDoCompileInfo76;

// } END

BOOL APIENTRY DllMain(HINSTANCE hinst,
    DWORD dwReason,
    LPVOID pReserved
    ) {
  switch (dwReason) {
  case DLL_PROCESS_ATTACH:
    ghInst = hinst;
    break;
  }
  return TRUE;
}

extern "C" __declspec(dllexport) int xCompileEntry(int selector,
    compStdParms* stdParms,
    long param1,
    long param2) {
  int result = comp_ErrNone;

  switch (selector) {
  case compStartup:
    result = compSDKStartup(stdParms, (compInfoRec*)param1);
    break;

  case compGetIndFormat:
    result = compSDKGetIndFormat(stdParms, (compFileInfoRec*)param1, param2);
    break;

  case compGetAudioIndFormat:
    result = compSDKGetAudioIndFormat(stdParms, (compAudioInfoRec*)param1, param2);
    break;

  case compDoCompile:
    result = compSDKDoCompile(stdParms, (compDoCompileInfo*)param1);
    break;

  case compGetAudioIndFormat7:
    result = compSDKGetAudioIndFormat7(stdParms, (compAudioInfoRec7*)param1, param2);
    break;

  case compDoCompile7:
    result = compSDKDoCompile7(stdParms, (compDoCompileInfo7*)param1);
    break;

  default:
    if (prPro)
      result = comp_Unsupported;
    break;
  }

  return result;
}

int compSDKGetAudioIndFormat(compStdParms* stdParms,
    compAudioInfoRec* audioInfoRec,
    int idx) {
  int returnValue = comp_ErrNone;

  audioInfoRec->audioFormats = (compAudioFormat*)stdParms->funcs->memoryFuncs->newPtr(4 * sizeof(compAudioFormat));
  compAudioFormatPtr audio = audioInfoRec->audioFormats;

  switch (idx) {
  case 0:
    audioInfoRec->subtype = compUncompressed;
    audioInfoRec->hasSetup = 0;
    strcpy(audioInfoRec->name, "Uncompressed");

    audio->audioDepths = comp16Stereo;
    audio->audioRate = 16000;
    audio++;
    audio->audioDepths = comp16Stereo;
    audio->audioRate = 32000;
    audio++;
    audio->audioDepths = comp16Stereo;
    audio->audioRate = 44100;
    audio++;
    audio->audioDepths = comp16Stereo;
    audio->audioRate = 48000;
    audio++;
    break;

  default:
    returnValue = comp_BadFormatIndex;
  }

  return returnValue;
}

int compSDKGetAudioIndFormat7(compStdParms* stdParms,
    compAudioInfoRec7* audioInfoRec,
    int idx) {
  int returnValue = comp_ErrNone;

  audioInfoRec->audioFormats = (compAudioFormat7*)stdParms->funcs->memoryFuncs->newPtr(4 * sizeof(compAudioFormat7));
  audioInfoRec->numFormats = 4;
  compAudioFormat7Ptr audio = audioInfoRec->audioFormats;

  switch (idx) {
  case 0:
    audioInfoRec->subtype = compUncompressed;
    audioInfoRec->hasSetup = 0;
    strcpy(audioInfoRec->name, "Uncompressed");
    audioInfoRec->fileType = 'AVIV';

    audio->sampleType = kAudioSampleType_16BitInt;
    audio->sampleRate = 16000;
    audio->channelType = kAudioChannelType_Stereo;
    audio++;
    audio->sampleType = kAudioSampleType_16BitInt;
    audio->sampleRate = 32000;
    audio->channelType = kAudioChannelType_Stereo;
    audio++;
    audio->sampleType = kAudioSampleType_16BitInt;
    audio->sampleRate = 44100;
    audio->channelType = kAudioChannelType_Stereo;
    audio++;
    audio->sampleType = kAudioSampleType_16BitInt;
    audio->sampleRate = 48000;
    audio->channelType = kAudioChannelType_Stereo;
    audio++;
    break;

  default:
    returnValue = comp_BadFormatIndex;
  }

  return returnValue;
}

// Process compGetIndFormat

int compSDKGetIndFormat(compStdParms* stdParms,
    compFileInfoRec* fileInfoRec,
    int idx) {
  int returnValue = comp_ErrNone;
  char subType[255] = APPNAME;

  switch (idx) {
  case 0:
    fileInfoRec->canDelta = false;                                  // The format supports "tween" frames, not keyframes only
    fileInfoRec->canDoFields = false;                              // If true, can write field ordered frames
    fileInfoRec->canDoQuality = false;                          // Compression "quality" settings (lossy formats setting)
    fileInfoRec->defaultQuality = 100;                          // The quality slider value default
    fileInfoRec->canForceKeyframes = 1;                          // Can set all frames to be keyframes
    fileInfoRec->canSetDataRate = 0;                              // Allows the Data rate options in Compiler to be set
    fileInfoRec->defaultKeyFrameRate = 15;                      // Default Keyframe rate (see Keyframe and Rendering Options)
    fileInfoRec->depthsSupported = compDepth24;                      // valid choices are 1,4,8,16,24,32,any. If multiple, | them together
    fileInfoRec->hasSetup = false;                                  // Set this true (1) to allow the compressor to pop it's own dialog.
    fileInfoRec->maxHeight = 8192;                              // Plug-in allowed upper limit.
    fileInfoRec->maxWidth = 8192;                              // Plug-in allowed upper limit.
    fileInfoRec->minHeight = 4;                                  // Min allowed pixel Height by Premiere
    fileInfoRec->minWidth = 4;                                  // Min allowed pixel Width by Premiere
    strcpy(fileInfoRec->name, subType);               // The name for this subtype (compressor)
    fileInfoRec->subtype = 'DFSC';
    break;

  // Tell Premiere we're done enumerating Video format compression types

  default:
    returnValue = comp_BadFormatIndex;
    break;
  }
  return returnValue;
}

int compSDKStartup(compStdParms* stdParms,
    compInfoRec* infoRec) {
  int err = comp_ErrNone;

  char theName[256] = APPNAME;

  // Premiere 6.0 is version 3 of the compiler interface

  if (stdParms->compInterfaceVer >= COMPILEMOD_VERSION_3) {
    // All custom compilers must set this true

    // infoRec->customExport        =    true;

    infoRec->classID = 'DTEK';                          // Class ID of the MAL (media abstraction layer)
    infoRec->fileType = 'DFSC';                          // The Filetype FCC (Four Character Code)
    strcpy(infoRec->compilerName, theName);           // Displayed name for the Compiler
    infoRec->compilesAudio = 1;                          // Can compile Audio, enables the Audio checkbox in File > Export > Movie
    infoRec->compilesVideo = 1;                          // Can compile Video, enables the Video checkbox in File > Export > Movie
    infoRec->hasSetup = FALSE;                          // Compiler has a private setup dialog (advanced)
    infoRec->singleFrame = FALSE;                      // If 1, is a single frame per file compiler (e.g. TIF, BMP, etc.)
    infoRec->canOpen = FALSE;                          // Set this to be able to open your own file handle
    if (stdParms->compInterfaceVer >= COMPILEMOD_VERSION_4)
      prPro = true;
    workaroundPremElementsBug = (stdParms->compInterfaceVer == COMPILEMOD_VERSION_6);
    infoRec->version = (prPro) ? COMPILEMOD_VERSION_4 : COMPILEMOD_VERSION_3;               // The version of the interface I'm requesting
  } else {
    err = comp_ErrOther;
  }
  return err;
}

int compSDKDoCompileCommon(compStdParms* stdParms, compDoCompileInfo* infoRec, compDoCompileInfo7* infoRec7) {
  compOutputRec settings;
  TCHAR filename[MAX_PATH];
  compDoCompileInfo76* infoRec76 = NULL;

  if (workaroundPremElementsBug && infoRec7)
    infoRec76 = (compDoCompileInfo76*)infoRec7;

  CloseHandle((infoRec) ? infoRec->outputFileRef :
      ((infoRec76) ? infoRec76->outputFileRef : infoRec7->outputFileRef));
  strcpy(filename, (char*)((infoRec) ? infoRec->outputFile.name :
                           ((infoRec76) ? infoRec76->outputFile.name : infoRec7->outputFile.name)));
  DeleteFile(filename);
  if (filename[strlen(filename) - 1] == '.')
    filename[strlen(filename) - 1] = 0;
  if (strlen(filename) < 4 || strncmp(filename + strlen(filename) - 4, ".avi", 4) != 0)
    strcat(filename, ".avi");
  DWORD nfvideo;
  if (infoRec) {
    nfvideo = infoRec->endFrame - infoRec->startFrame;
    settings = infoRec->outputRec;
  } else if (infoRec76) {
    nfvideo = infoRec76->endFrame - infoRec76->startFrame;
    settings.doVideo = infoRec76->outputRec.doVideo;
    settings.doAudio = infoRec76->outputRec.doAudio;
    settings.audCompression = infoRec76->outputRec.audCompression;
    settings.width = infoRec76->outputRec.width;
    settings.height = infoRec76->outputRec.height;
    settings.timebase = infoRec76->outputRec.timebase;
    settings.audrate = (unsigned long)infoRec76->outputRec.sampleRate;
    settings.audsamplesize = (infoRec76->outputRec.sampleType == kAudioSampleType_16BitInt) ? 16 : 8;
    settings.stereo = (infoRec76->outputRec.channelType == kAudioChannelType_Stereo) ? 1 : 0;
  } else {
    nfvideo = infoRec7->endFrame - infoRec7->startFrame;
    settings.doVideo = infoRec7->outputRec.doVideo;
    settings.doAudio = infoRec7->outputRec.doAudio;
    settings.audCompression = infoRec7->outputRec.audCompression;
    settings.width = infoRec7->outputRec.width;
    settings.height = infoRec7->outputRec.height;
    settings.timebase = infoRec7->outputRec.timebase;
    settings.audrate = (unsigned long)infoRec7->outputRec.sampleRate;
    settings.audsamplesize = (infoRec7->outputRec.sampleType == kAudioSampleType_16BitInt) ? 16 : 8;
    settings.stereo = (infoRec7->outputRec.channelType == kAudioChannelType_Stereo) ? 1 : 0;
  }
  if (settings.audrate <= 0) settings.audrate = 48000;
  double fps = (double)settings.timebase.scale / settings.timebase.sampleSize;

  PremiereFSImpl fs;
  fs.Init(!!settings.doAudio, settings.audrate, settings.audsamplesize, ((settings.stereo) ? 2 : 1), nfvideo, fps,
      settings.width, settings.height, GetActiveWindow(), filename);
  fs.sp = stdParms;
  fs.pFrame = NULL;
  fs.irec = infoRec;
  fs.audioBuffer = NULL;
  fs.irec7 = infoRec7;
  fs.CompAudioSuite = NULL;
  fs.audioFloatData[0] = NULL;
  fs.audioFloatData[1] = NULL;
  fs.audioFloatDataPos = 0;
  if (infoRec7) {
    SPBasicSuite* SPBasic = stdParms->piSuites->utilFuncs->getSPBasicSuite();
    SPBasic->AcquireSuite(kPrSDKCompilerRenderSuite, kPrSDKCompilerRenderSuiteVersion, (const void**)&fs.CompRenderSuite);
    SPBasic->AcquireSuite(kPrSDKCompilerAudioSuite, kPrSDKCompilerAudioSuiteVersion, (const void**)&fs.CompAudioSuite);
    if (settings.doAudio) {
      fs.audioFloatData[0] = (float*)stdParms->piSuites->memFuncs->newPtr(settings.audrate * sizeof(float));
      fs.audioFloatData[1] = (float*)stdParms->piSuites->memFuncs->newPtr(settings.audrate * sizeof(float));
      if (!fs.audioFloatData[0] || !fs.audioFloatData[1])
        return comp_ErrMemory;
    }
  }
  bool rval = fs.Run();
  if (infoRec7) {
    stdParms->piSuites->memFuncs->disposePtr((char*)fs.audioFloatData[0]);
    stdParms->piSuites->memFuncs->disposePtr((char*)fs.audioFloatData[1]);
  }
  if (!rval)
    return comp_CompileAbort;
  return comp_ErrNone;
}

int compSDKDoCompile(compStdParms* stdParms, compDoCompileInfo* infoRec) {
  return compSDKDoCompileCommon(stdParms, infoRec, NULL);
}

int compSDKDoCompile7(compStdParms* stdParms, compDoCompileInfo7* infoRec) {
  return compSDKDoCompileCommon(stdParms, NULL, infoRec);
}

void PremiereFSImpl::OnVideoRequest() {
  PrPixelFormat pixelFormat[] = { PrPixelFormat_VUYA32, PrPixelFormat_BGRA32 };
  compGetFrameReturnRec getFrameReturnRec;
  long rowBytes = 0;
  int returnValue;

  if (irec) {
    returnValue = sp->funcs->videoFuncs->getFrame(irec->startFrame + vars->videoFrameIndex,
        &pFrame, &rowBytes, &getFrameReturnRec,
        0, irec->compileSeqID);
  } else {
    compDoCompileInfo76* irec76 =
      (workaroundPremElementsBug && irec7) ? (compDoCompileInfo76*)irec7 : NULL;
    long startFrame = (irec76) ? irec76->startFrame : irec7->startFrame;
    long compileSeqID = (irec76) ? irec76->compileSeqID : irec7->compileSeqID;

    returnValue = CompRenderSuite->GetFrame(startFrame + vars->videoFrameIndex,
        &pFrame, &rowBytes,
        pixelFormat + ((serveFormat == sfYUY2) ? 0 : 1),
        (serveFormat == sfYUY2) ? 2 : 1,
        &getFrameReturnRec,
        0, compileSeqID);
  }

  if (returnValue == comp_ErrNone) {
    ConvertVideoFrame(pFrame, rowBytes, vars,
        (irec) ? idfRGB32 : ((getFrameReturnRec.bufferPixelFormat == PrPixelFormat_VUYA32) ? idfAYUV : idfRGB32));
  } else {
    vars->videoBytesRead = width * height * vars->encBi.biBitCount / 8;
    memset(((LPBYTE)vars) + vars->videooffset, 0, vars->videoBytesRead);
  }
}

void PremiereFSImpl::OnAudioRequest() {
  ULONG ccToRead = audioSamplingRate / 100, ccActuallyRead = 0;
  ULONG curpos = vars->audioFrameIndex * ccToRead;
  int returnValue;

  if (irec) {
    double stframe = ((double)vars->audioFrameIndex / 100) * fps;
    double endframe = ((double)(vars->audioFrameIndex + 1) / 100) * fps;
    int stframesample = (int)(audioSamplingRate * floor(stframe) / fps);

    int blockalign = (audioBitsPerSample * audioChannels) / 8;
    int offintobuf = curpos - stframesample;
    long audioBytesRead = 0;
    long audioOffset = 0, audioFrameCount = (int)(ceil(endframe) - floor(stframe));

    vars->audioBytesRead = 0;
    if (hasAudio) {
      returnValue = sp->funcs->audioFuncs->getAudio(
          irec->startFrame + (int)floor(stframe),
          &audioFrameCount,
          &audioBytesRead,
          audioOffset,
          &audioBuffer,
          irec->compileSeqID);

      if (returnValue == comp_ErrNone) {
        memcpy(((LPBYTE)vars) + vars->audiooffset, *audioBuffer + offintobuf * blockalign, ccToRead * blockalign);
      } else {
        memset(((LPBYTE)vars) + vars->audiooffset, 0, ccToRead * blockalign);
      }
      vars->audioBytesRead = ccToRead * blockalign;
    }
  } else {
    if (hasAudio && audioFloatData[0]) {
      if (curpos < audioFloatDataPos) {
        audioFloatDataPos = 0;
        CompAudioSuite->ResetAudioToBeginning(irec7->compilerID);
      }
      while (audioFloatDataPos < curpos) {
        CompAudioSuite->GetAudio(ccToRead, audioFloatData, irec7->compilerID);
        audioFloatDataPos += ccToRead;
      }
      for (unsigned i = 0; i < ccToRead; i++) {
        audioFloatData[0][i] = 0.0f;
        audioFloatData[1][i] = 0.0f;
      }
      returnValue = CompAudioSuite->GetAudio(ccToRead, audioFloatData, irec7->compilerID);
      audioFloatDataPos += ccToRead;

      short* audioShortData = (short*)(((LPBYTE)vars) + vars->audiooffset);
      for (i = 0; i < ccToRead; i++) {
        audioShortData[i * audioChannels + 0] = (short)(audioFloatData[0][i] * 32767);
        audioShortData[i * audioChannels + 1] = (short)(audioFloatData[1][i] * 32767);
      }
    } else {
      short* audioShortData = (short*)(((LPBYTE)vars) + vars->audiooffset);
      for (DWORD i = 0; i < ccToRead; i++) {
        audioShortData[i * audioChannels + 0] = 0;
        audioShortData[i * audioChannels + 1] = 0;
      }
    }
    int blockalign = (audioBitsPerSample * audioChannels) / 8;
    vars->audioBytesRead = ccToRead * blockalign;
  }
}

bool PremiereFSImpl::OnAudioRequestOneSecond(DWORD second, LPBYTE* data, DWORD* datalen) {
  if (irec7) {
    DWORD ccToRead = (DWORD)(irec7->outputRec.sampleRate);
    DWORD curpos = second * ccToRead;

    if (curpos < audioFloatDataPos) {
      audioFloatDataPos = 0;
      CompAudioSuite->ResetAudioToBeginning(irec7->compilerID);
    }
    while (audioFloatDataPos < curpos) {
      CompAudioSuite->GetAudio(ccToRead, audioFloatData, irec7->compilerID);
      audioFloatDataPos += ccToRead;
    }
    for (unsigned i = 0; i < ccToRead; i++) {
      audioFloatData[0][i] = 0.0f;
      audioFloatData[1][i] = 0.0f;
    }
    short* audioShortData = (short*)*data;
    if (!audioShortData)
      audioShortData = (short*)calloc(1, ccToRead * audioChannels * sizeof(short));
    CompAudioSuite->GetAudio(ccToRead, audioFloatData, irec7->compilerID);
    for (unsigned j = 0; j < ccToRead; j++) {
      audioShortData[j * audioChannels + 0] = (short)(audioFloatData[0][j] * 32767);
      audioShortData[j * audioChannels + 1] = (short)(audioFloatData[1][j] * 32767);
    }
    audioFloatDataPos += ccToRead;

    *data = (unsigned char*)audioShortData;
    *datalen = ccToRead * audioChannels * sizeof(short);
  } else {
    ULONG ccToRead = irec->outputRec.audrate, ccActuallyRead = 0;
    ULONG curpos = second * ccToRead;

    double fps = (double)irec->outputRec.timebase.scale / irec->outputRec.timebase.sampleSize;
    double stframe = ((double)second) * fps;
    double endframe = ((double)(second + 1)) * fps;
    int stframesample = (int)(irec->outputRec.audrate * floor(stframe) / fps);

    int blockalign = (irec->outputRec.audsamplesize * (1 + (!!irec->outputRec.stereo))) / 8;
    int offintobuf = curpos - stframesample;

    long audioOffset = 0;
    long audioFrameCount = (int)(ceil(endframe) - floor(stframe));
    long frameIndex = (int)floor(stframe);

    *datalen = (int)(irec->outputRec.audrate / fps * (audioFrameCount + 1) * 4);
    if (!*data)
      *data = (unsigned char*)calloc(1, *datalen);
    offintobuf *= blockalign;

    for (int frameCount = 0; frameCount < audioFrameCount;) {
      long audioBytesRead = 0;
      long fc = audioFrameCount - frameCount;
      int returnValue = sp->funcs->audioFuncs->getAudio(
          irec->startFrame + frameIndex,
          &fc,
          &audioBytesRead,
          0,
          &audioBuffer,
          irec->compileSeqID);
      frameCount += fc;
      frameIndex += fc;
      int oib = (offintobuf < audioBytesRead) ? offintobuf : audioBytesRead;
      if (returnValue == comp_ErrNone) {
        memcpy(*data + audioOffset, *audioBuffer + oib, audioBytesRead - oib);
      }
      audioOffset += audioBytesRead - oib;
      offintobuf -= oib;
      if (offintobuf < 0) offintobuf = 0;
    }

    *datalen = ccToRead * blockalign;
  }
  return true;
}
